package com.cmc6.websocket.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.Header;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc6.common.config.BaseConfig;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.exception.BaseBizCodeEnum;
import com.cmc6.common.mapper.BaseUserInfoMapper;
import com.cmc6.common.model.dto.ChangeByteById;
import com.cmc6.common.model.dto.ChangeByteDTO;
import com.cmc6.common.model.enums.SocketMessageEnum;
import com.cmc6.common.util.ApiResult;
import com.cmc6.common.util.IpUtil;
import com.cmc6.common.util.MyJwtUtil;
import com.cmc6.common.util.UserUtil;
import com.cmc6.websocket.config.WebSocketApplicationRunnerConfig;
import com.cmc6.websocket.constant.CommonConstant;
import com.cmc6.websocket.mapper.SocketMapper;
import com.cmc6.websocket.model.dto.SocketPageDTO;
import com.cmc6.websocket.model.entity.SocketDO;
import com.cmc6.websocket.model.enums.SocketTypeEnum;
import com.cmc6.websocket.model.vo.SocketPageVO;
import com.cmc6.websocket.model.vo.SocketRegVO;
import com.cmc6.websocket.service.WebSocketHelperService;
import io.netty.channel.Channel;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

@Service
public class WebSocketHelperServiceImpl implements WebSocketHelperService {

    @Resource
    SocketMapper socketMapper;
    @Resource
    BaseUserInfoMapper baseUserInfoMapper;
    @Resource
    HttpServletRequest request;
    @Resource
    RedisTemplate<String, SocketDO> redisTemplate;

    /**
     * 有人 上/下线 之后的处理
     */
    @Override
    public void handleSocketEvent(Long socketId, Channel channel) {
        if (channel != null) {
            // 已经连接
            SocketMessageEnum socketMessageEnum = SocketMessageEnum.SOCKET_ID;
            socketMessageEnum.setJson(JSONUtil.createObj().set(CommonConstant.SOCKET_ID, socketId));
            channel.writeAndFlush(socketMessageEnum.toSocketText());
        }
    }

    /**
     * 获取 socket连接地址和随机码
     */
    @Override
    public SocketRegVO reg(ChangeByteDTO changeByteDTO) {

        Byte value = changeByteDTO.getValue();

        SocketDO socketDO = new SocketDO(); // 存放到redis，用于匹配连接

        // 判断前端选择的是 在线还是隐身
        if (CommonConstant.WEB_SOCKET_ONLINE_KEY == value) {
            socketDO.setType(SocketTypeEnum.getByCode(value));
        } else if (CommonConstant.WEB_SOCKET_HIDDEN_KEY == value) {
            socketDO.setType(SocketTypeEnum.getByCode(value));
        } else {
            ApiResult.error(BaseBizCodeEnum.PARAMETER_CHECK_ERROR);
        }

        setForRegRedis(socketDO); // 设置：放到 redis中注册信息对象的 属性

        String randomNum = RandomUtil.randomString(16).toUpperCase(); // 16位随机数

        // 放到 redis的 reg里面，一分钟自动过期
        ValueOperations<String, SocketDO> ops = redisTemplate.opsForValue();

        ops.set(WebSocketApplicationRunnerConfig.socketKey + randomNum, socketDO, BaseConstant.MINUTE_1_EXPIRE_TIME,
            TimeUnit.MILLISECONDS); // WEB_SOCKET_REDIS_KEY:ip:port:randomNum

        String socketUrl =
            BaseConfig.prodFlag ? BaseConfig.socketAddress : WebSocketApplicationRunnerConfig.socketSufKey;

        return getRegResult(randomNum, socketUrl);
    }

    /**
     * 获取 socket连接地址和随机码：封装返回值
     */
    private SocketRegVO getRegResult(String randomNum, String socketUrl) {
        SocketRegVO resVO = new SocketRegVO();
        resVO.setSocketUrl(socketUrl);
        resVO.setCode(randomNum);
        return resVO;
    }

    /**
     * 分页排序查询 socket连接记录
     */
    @Override
    public Page<SocketPageVO> myPage(SocketPageDTO dto) {

        return socketMapper.myPage(dto.getPage(), dto);
    }

    /**
     * 更改在线状态
     */
    @Override
    @Transactional
    public String changeType(ChangeByteById changeByteById) {

        SocketDO entity = new SocketDO();

        entity.setId(changeByteById.getId());
        entity.setType(SocketTypeEnum.getByCode(changeByteById.getValue()));

        socketMapper.updateById(entity);

        return BaseBizCodeEnum.API_RESULT_OK.getMsg();
    }

    /**
     * {@link com.cmc6.websocket.config.MyWebSocketHandler#channelRead}
     * 设置：放到 redis中注册信息对象的 属性
     */
    private void setForRegRedis(SocketDO socketDO) {

        socketDO.setUserId(UserUtil.getCurrentUserId());

        String ip = ServletUtil.getClientIP(request);
        socketDO.setIp(ip);

        String uaStr = request.getHeader(Header.USER_AGENT.getValue());

        UserAgent ua = UserAgentUtil.parse(uaStr);

        socketDO.setRegion(IpUtil.getRegion(ip));
        socketDO.setBrowser(ua.getBrowser().toString() + "/" + ua.getVersion());
        socketDO.setOs(ua.getOs().toString());
        socketDO.setMobileFlag(ua.isMobile());

        // 备注：这里不用担心 jwt不存在的问题，因为 JwtAuthorizationFilter已经处理过了，所以这里一定会有 jwt
        socketDO.setJwtHash(MyJwtUtil.generateRedisJwtHash(request.getHeader(BaseConstant.JWT_HEADER_KEY)));

    }

}
